/**
 * Licensed under the Artistic License; you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://displaytag.sourceforge.net/license.html
 *
 * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
package canglang.scan.core.base.taglib.displaytag.tags;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import canglang.scan.core.base.properties.AppDefaultProperty;
import canglang.scan.core.base.taglib.displaytag.exception.ObjectLookupException;
import canglang.scan.core.base.taglib.displaytag.exception.TagStructureException;
import canglang.scan.core.base.taglib.displaytag.model.Cell;
import canglang.scan.core.base.taglib.displaytag.model.HeaderCell;
import canglang.scan.core.base.taglib.displaytag.util.DefaultHref;
import canglang.scan.core.base.taglib.displaytag.util.Href;
import canglang.scan.core.base.taglib.displaytag.util.HtmlAttributeMap;
import canglang.scan.core.base.taglib.displaytag.util.MultipleHtmlAttribute;
import canglang.scan.core.base.taglib.displaytag.util.TagConstants;


/**
 * <p>
 * This tag works hand in hand with the TableTag to display a list of objects. This describes a column of data in the
 * TableTag. There can be any number of columns that make up the list.
 * </p>
 * <p/>
 * This tag does no work itself, it is simply a container of information. The TableTag does all the work based on the
 * information provided in the attributes of this tag.
 * <p/>
 *
 * @author mraible
 * @author Fabrizio Giustina
 * @version $Revision: 1160 $ ($Author: fgiust $)
 */
public class ColumnTag extends BodyTagSupport {

    /**
     * D1597A17A6.
     */
    private static final long serialVersionUID = 899149338534L;

    /**
     * logger.
     */
    private static Log log = LogFactory.getLog(ColumnTag.class);

    /**
     * html pass-through attributes for cells.
     */
    private HtmlAttributeMap attributeMap = new HtmlAttributeMap();

    /**
     * html pass-through attributes for cell headers.
     */
    private HtmlAttributeMap headerAttributeMap = new HtmlAttributeMap();

    /**
     * the property method that is called to retrieve the information to be displayed in this column. This method is
     * called on the current object in the iteration for the given row. The property format is in typical struts format
     * for properties (required)
     */
    private String property;

    /**
     * the title displayed for this column. if this is omitted then the property name is used for the title of the
     * column (optional).
     */
    private String title;



    /**
     * is the column sortable?
     */
    private boolean sortable;

    /**
     * Name given to the server when sorting this column.
     */
    private String sortName;



    /**
     * if this attribute is provided, then the data that is shown for this column is wrapped inside a &lt;a href&gt; tag
     * with the url provided through this attribute. Typically you would use this attribute along with one of the
     * struts-like param attributes below to create a dynamic link so that each row creates a different URL based on the
     * data that is being viewed. (optional)
     */
    private Href href;

    /**
     * is the column already sorted?
     */
    private boolean alreadySorted;

    /**
     * Property in a resource bundle to be used as the title for the column.
     */
    private String titleKey;

    /**
     * The name of the bean property if a decorator is used and sorting need to be still on on the property itself.
     * Useful for displaying data with links but sorting on original value.
     */
    private String sortProperty;

    /**
     * Should the value of the column be summed? Requires that the value of the column be convertible to a Number.
     */
    private boolean totaled;

    /**
     * Static value for this cell, equivalent to column body.
     */
    private Object value;

    /**
     * Setter for totals.
     *
     * @param totals the value
     */
    public void setTotal(boolean totals) {
        this.totaled = totals;
    }

    /**
     * setter for the "property" tag attribute.
     *
     * @param value attribute value
     */
    public void setProperty(String value) {
        this.property = value;
    }

    /**
     * setter for the "value" tag attribute.
     *
     * @param value attribute value
     */
    public void setValue(Object value) {
        this.value = value;
    }


    /**
     * setter for the "title" tag attribute.
     *
     * @param value attribute value
     */
    public void setTitle(String value) {
        this.title = value;
    }

    /**
     * setter for the "sortable" tag attribute.
     *
     * @param value attribute value
     */
    public void setSortable(boolean value) {
        this.sortable = value;
    }



    /**
     * setter for the "titleKey" tag attribute.
     *
     * @param value property name
     */
    public void setTitleKey(String value) {
        this.titleKey = value;
    }

    /**
     * setter for the "href" tag attribute.
     *
     * @param value attribute value
     */
    public void setHref(String value) {
        // call encodeURL to preserve session id when cookies are disabled
        String encodedHref = ((HttpServletResponse) this.pageContext.getResponse()).encodeURL(StringUtils
                .defaultString(value));
        this.href = new DefaultHref(encodedHref);
    }

    /**
     * setter for the "url" tag attribute. This has the same meaning of href, but prepends the context path to the given
     * URI.
     *
     * @param value attribute value
     */
    public void setUrl(String value) {
        HttpServletRequest req = (HttpServletRequest) pageContext.getRequest();
        // call encodeURL to preserve session id when cookies are disabled
        String encodedHref = ((HttpServletResponse) this.pageContext.getResponse()).encodeURL(StringUtils
                .defaultString(req.getContextPath() + value));
        this.href = new DefaultHref(encodedHref);
    }


    /**
     * setter for the "scope" tag attribute.
     *
     * @param value attribute value
     */
    public void setScope(String value) {
        this.attributeMap.put(TagConstants.ATTRIBUTE_SCOPE, value);
    }

    /**
     * setter for the "headerScope" tag attribute.
     *
     * @param value attribute value
     */
    public void setHeaderScope(String value) {
        this.headerAttributeMap.put(TagConstants.ATTRIBUTE_SCOPE, value);
    }


    /**
     * setter for the "style" tag attribute.
     *
     * @param value attribute value
     */
    public void setStyle(String value) {
        this.attributeMap.put(TagConstants.ATTRIBUTE_STYLE, value);
        this.headerAttributeMap.put(TagConstants.ATTRIBUTE_STYLE, value);
    }

    /**
     * setter for the "class" tag attribute.
     *
     * @param value attribute value
     */
    public void setStyleClass(String value) {
        this.attributeMap.put(TagConstants.ATTRIBUTE_CLASS, new MultipleHtmlAttribute(value));
    }

    /**
     * setter for the "width" tag attribute.
     *
     * @param value attribute value
     */
    public void setWidth(String value) {
        this.headerAttributeMap.put(TagConstants.ATTRIBUTE_WIDTH, new MultipleHtmlAttribute(value));
    }

    /**
     * setter for the "headerClass" tag attribute.
     *
     * @param value attribute value
     */
    public void setHeaderClass(String value) {
        this.headerAttributeMap.put(TagConstants.ATTRIBUTE_CLASS, new MultipleHtmlAttribute(value));
    }


    /**
     * setter for the "sortProperty" tag attribute.
     *
     * @param value attribute value
     */
    public void setSortProperty(String value) {
        this.sortProperty = value;
    }

    /**
     * Looks up the parent table tag.
     *
     * @return a table tag instance.
     */
    protected TableTag getTableTag() {
        return (TableTag) findAncestorWithClass(this, TableTag.class);
    }

    /**
     * sets the name given to the server when sorting this column
     *
     * @param sortName name given to the server to sort this column
     */
    public void setSortName(String sortName) {
        this.sortName = sortName;
    }

    public void setNowrap(String value) {
        if (value != null && !"".equalsIgnoreCase(value.trim()) &&
                (value.trim().equalsIgnoreCase("nowrap") ||
                        value.trim().equalsIgnoreCase("true") ||
                        value.trim().equalsIgnoreCase("Y"))) {
            this.attributeMap.put(TagConstants.ATTRIBUTE_NOWRAP, "nowrap");
        }

    }

    /**
     * Passes attribute information up to the parent TableTag.
     * <p>
     * When we hit the end of the tag, we simply let our parent (which better be a TableTag) know what the user wants to
     * do with this column. We do that by simple registering this tag with the parent. This tag's only job is to hold
     * the configuration information to describe this particular column. The TableTag does all the work.
     * </p>
     *
     * @return int
     * @throws JspException if this tag is being used outside of a &lt;display:list...&gt; tag.
     * @see javax.servlet.jsp.tagext.Tag#doEndTag()
     */
    public int doEndTag() throws JspException {
        TableTag tableTag = getTableTag();

        // add column header only once
        if (tableTag.isFirstIteration()) {
            addHeaderToTable(tableTag);
        }

        if (!tableTag.isIncludedRow()) {
            return super.doEndTag();
        }

        Cell cell = null;
        if (this.bodyContent != null) {
            cell = new Cell(this.bodyContent.getString());
        }

        Object rowStyle = this.attributeMap.get(TagConstants.ATTRIBUTE_STYLE);
        Object rowClass = this.attributeMap.get(TagConstants.ATTRIBUTE_CLASS);
        if (rowStyle != null || rowClass != null) {
            HtmlAttributeMap perRowValues = new HtmlAttributeMap();
            if (rowStyle != null) {
                perRowValues.put(TagConstants.ATTRIBUTE_STYLE, rowStyle);
            }
            if (rowClass != null) {
                perRowValues.put(TagConstants.ATTRIBUTE_CLASS, rowClass);
            }
            if (cell == null) {
                cell = new Cell(null);
            }
            cell.setPerRowAttributes(perRowValues);
        }

        tableTag.addCell(cell != null ? cell : Cell.EMPTY_CELL);

        // cleanup non-attribute variables
        this.alreadySorted = false;

        return super.doEndTag();
    }

    /**
     * Adds the current header to the table model calling addColumn in the parent table tag. This method should be
     * called only at first iteration.
     *
     * @param tableTag parent table tag
     *                               for error during column decorator instantiation
     * @throws ObjectLookupException for errors in looking up values
     */
    private void addHeaderToTable(TableTag tableTag) throws  ObjectLookupException {
        // don't modify "title" directly
        String evalTitle = this.title;

        // title has precedence over titleKey
        if (evalTitle == null && this.titleKey != null) {
            // handle title i18n
            evalTitle = AppDefaultProperty.getProperty(titleKey);
        }



        HeaderCell headerCell = new HeaderCell();
        headerCell.setHeaderAttributes((HtmlAttributeMap) this.headerAttributeMap.clone());
        headerCell.setHtmlAttributes((HtmlAttributeMap) this.attributeMap.clone());
        headerCell.setTitle(evalTitle);
        headerCell.setSortable(this.sortable);
        headerCell.setBeanPropertyName(this.property);
        headerCell.setSortProperty(this.sortProperty);
        headerCell.setTotaled(this.totaled);
        headerCell.setSortName(this.sortName);

        // href and parameter, create link
        if (this.href != null) {
            Href colHref;

            // empty base url, use href with parameters from parent table
            if (StringUtils.isEmpty(this.href.getBaseUrl())) {
                colHref = (Href) tableTag.getBaseHref().clone();
            } else {
                colHref = (Href) this.href.clone();
            }

            // sets the base href
            headerCell.setHref(colHref);

        }

        tableTag.addColumn(headerCell);

        if (log.isDebugEnabled()) {
            log.debug("columnTag.addHeaderToTable() :: first iteration - adding header " + headerCell);
        }
    }

    /**
     * @see javax.servlet.jsp.tagext.Tag#release()
     */
    public void release() {
        super.release();
        this.attributeMap.clear();
        this.headerAttributeMap.clear();
        this.href = null;
        this.property = null;
        this.sortable = false;
        this.sortName = null;
        this.title = null;
        this.titleKey = null;
        this.sortProperty = null;
        this.value = null;
        this.totaled = false;
    }

    /**
     * @see javax.servlet.jsp.tagext.Tag#doStartTag()
     */
    public int doStartTag() throws JspException {
        TableTag tableTag = getTableTag();
        if (tableTag == null) {
            throw new TagStructureException(getClass(), "column", "table");
        }

        // If the list is empty, do not execute the body; may result in NPE
        if (tableTag.isEmpty() || !tableTag.isIncludedRow()) {
            return SKIP_BODY;
        }

        return super.doStartTag();
    }

    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) //
                .append("bodyContent", this.bodyContent) //$NON-NLS-1$
                .append("href", this.href) //$NON-NLS-1$
                .append("title", this.title) //$NON-NLS-1$
                .append("property", this.property) //$NON-NLS-1$
                .append("headerAttributeMap", this.headerAttributeMap) //$NON-NLS-1$
                .append("attributeMap", this.attributeMap) //$NON-NLS-1$
                .append("sortable", this.sortable) //$NON-NLS-1$
                .append("alreadySorted", this.alreadySorted) //$NON-NLS-1$
                .append("sortProperty", this.sortProperty) //$NON-NLS-1$
                .toString();
    }

}